% *******************************************
% SECTION
% *******************************************
\section{实验室环境搭建}

请从实验网站下载名为 \texttt{Labsetup.zip} 的文件到您的虚拟机中，解压后会得到一个名为 \texttt{Labsetup} 的文件夹。该文件夹内包含了完成本实验所需的所有文件。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{关闭防御措施}

在开始本实验之前，我们需要确保地址随机化防御措施已关闭；否则，攻击将会十分困难。
您可以使用以下命令来实现：

\begin{lstlisting}
$ sudo /sbin/sysctl -w kernel.randomize_va_space=0
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{存在漏洞的程序} 
\label{sec:vulnerable_program}

本次实验中用于攻击的目标程序名为 \texttt{stack.c}，位于 \texttt{server-code} 文件夹内。
该程序具有缓冲区溢出漏洞，您的任务是利用此漏洞获得 root 权限。下面列出的代码去除了部分非关键信息，所以与从实验设置文件获取的内容略有不同。

\begin{lstlisting}[language=C, caption={存在漏洞的程序 \texttt{stack.c}}]
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* 更改此值将改变堆栈布局。
 * 教师可以每年更改此值，以防止学生使用过往的答案。 */
#ifndef BUF_SIZE
#define BUF_SIZE 100
#endif

int bof(char *str)
{
    char buffer[BUF_SIZE];

    /* 下面的语句存在缓冲区溢出问题 */ 
(*@\ifdefined\arm
\ \ \ \ memcpy(buffer, str, 517);       
\else
\ \ \ \ strcpy(buffer, str);          
\fi@*)

    return 1;
}

void foo(char *str)
{
    ...
    bof(str);
}

int main(int argc, char **argv)
{
    char str[517];

    int length = fread(str, sizeof(char), 517, stdin);
    foo(str);
    fprintf(stdout, "==== Returned Properly ====\n");
    return 1;
}
\end{lstlisting}

上述程序存在缓冲区溢出漏洞。它从标准输入读取数据，然后将这些数据复制到 \texttt{bof()} 函数中的另一个缓冲区内。原始输入的最大长度为 \texttt{517} 字节，但 \texttt{bof()} 中的缓冲区只有 \texttt{BUF\_SIZE} 个字节长（小于 \texttt{517})。
\ifdefined\arm
当使用 \texttt{memcpy()} 将数据复制到目标缓冲区时，
else
因为 \texttt{strcpy()} 不会检查边界， 
\fi
会发生缓冲区溢出。

该程序将在具有 root 权限的服务器上运行，并将标准输入重定向到服务器与远程用户之间的 TCP 连接。因此，程序实际上是从远程用户处获取数据。如果攻击者能够利用此缓冲区溢出漏洞，他们就可以在服务器上获得 root shell。

\paragraph{编译。}
要编译上述存在漏洞的程序，需要使用 \texttt{-fno-stack-protector} 和 \texttt{"-z execstack"} 选项关闭 StackGuard 和非可执行堆栈保护。
下面是一个编译命令的例子（环境变量 \texttt{L1} 设定了 \texttt{stack.c} 中的 \texttt{BUF\_SIZE} 常量值）：

\begin{lstlisting}
$ gcc -DBUF_SIZE=$(L1) -o stack -z execstack -fno-stack-protector stack.c
\end{lstlisting}

\ifdefined\arm
\else
我们将把 \texttt{stack} 程序编译成 32 位和 64 位两个二进制文件。我们的预构建的 Ubuntu 20.04 虚拟机是 64 位虚拟机，但仍支持 32 位二进制文件。我们只需要在 \texttt{gcc} 命令中使用 \texttt{-m32} 选项来实现 32 位编译。
对于 32 位编译，我们也使用 \texttt{-static} 生成一个静态链接的可执行文件，该文件是自包含的且不依赖于任何动态库。因为我们的容器中没有安装 32 位动态库。
\fi

编译命令已提供在 \texttt{Makefile} 中。要编译代码，请输入 \texttt{make} 来执行这些命令。变量 \texttt{L1}、\texttt{L2}、\texttt{L3} 和 \texttt{L4} 在 \texttt{Makefile} 中被设置，并会在编译过程中使用。
编译完成后，需要将可执行文件复制到 \texttt{bof-containers} 文件夹中以便容器可以使用。以下命令完成编译和安装。

\begin{lstlisting}
$ make
$ make install
\end{lstlisting}

\paragraph{教师定制。}
为了使本次实验与过去的略有不同，教师可以要求学生使用不同的 \texttt{BUF\_SIZE} 值来重新编译服务器代码。在 \texttt{Makefile} 中，\texttt{BUF\_SIZE} 的值由四个变量 \texttt{L1}, ..., \texttt{L4} 设定。
教师可根据以下建议选择这些变量的值：

\begin{itemize}[noitemsep]
    \item \texttt{L1}: 选择一个介于 100 和 400 之间的数字
    \item \texttt{L2}: 选择一个介于 40 和 200 之间的数字
    \item \texttt{L3}: 选择一个介于 100 和 400 之间的数字
    \item \texttt{L4}: 选择一个介于 20 和 80 之间的数字；我们需要将这个值保持较小，使本级别比上一级别更具挑战性。 
\end{itemize}

\paragraph{服务器程序。}
在 \texttt{server-code} 文件夹中，可以找到名为 \texttt{server.c} 的程序。这是服务器的主要入口点。它侦听端口 \texttt{9090}。当接收到 TCP 连接时，会调用 \texttt{stack} 程序，并将该 TCP 连接作为标准输入提供给 \texttt{stack} 程序。这样，在 \texttt{stack} 读取标准输入数据时，实际上是读取了 TCP 连接上的数据，即这些数据由 TCP 客户端的用户提供。学生不需要阅读 \texttt{server.c} 的源代码。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器设置和命令}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/container/setup}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\paragraph{注意。} 在运行 \texttt{"docker-compose build"} 命令构建 Docker 镜像之前，我们需要编译并复制服务器代码到 \texttt{bof-containers} 文件夹中。
这一步骤已在 Section~\ref{sec:vulnerable_program} 中描述。